#ifndef __OBJECT_RECORD_SET_H__
  #define __OBJECT_RECORD_SET_H__

#include <vector>
#include <functional>

#include "str.h"
#include "database_serializable.h"
#include "useful_functions.h"
#include "safe_pointer.h"
#include "exceptions.h"

using namespace std;

//! Recordset for objects

/*!
This class represents a collection of objects, which are loaded from the database.

Implementation date: 2008

Usage:

\code
class person : public database_serializable {
private:
  string firstname;
  string lastname;
  string birthdate;
  bool sex;
  double height;

public:
  virtual void load() {
    DESERIALIZE(firstname);
    DESERIALIZE(lastname);
    DESERIALIZE(birthdate);
    DESERIALIZE(sex);
    DESERIALIZE(height);
  }

  virtual void persist(bool requires_lock = true) {
    SERIALIZE(firstname);
    SERIALIZE(lastname);
    SERIALIZE(birthdate);
    SERIALIZE(sex);
    SERIALIZE(height);
    database_serializable::persist(requires_lock);
  }

  person() : database_serializable("person") {
    // nothing to do
  }

  virtual ~person() {
    // nothing to do
  }
};

object_record_set<person> persons;
vector<person*> *lp = persons.get_objects("FROM person ORDER BY id");

if (lp->size() > 0) {
  (*lp)[0]->birthdate = "20381212";
  (*lp)[0]->persist();
}

lp = persons.get_objects("id, birthdate FROM person WHERE id = %1", arg<unsigned long long>(17));

if (lp->size() > 0) {
  (*lp)[0]->birthdate = "20381212";
  (*lp)[0]->persist();
}
\endcode
*/

template <class T>
class object_record_set {
private:
  vector<T*> objects;

  //! assignment operator: not allowed
  object_record_set &operator=(const object_record_set &ors);

  //! copy constructor: not allowed
  object_record_set(const object_record_set &ors);

public:
  //! clears the vector of objects and releases the allocated memory
  void clear();

  //! returns only the first object of the sql query
  /*!
  the returned safe_pointer will contain a null value, if nothing could be returned by the query, or it
  will be the first row returned by the query.
  */
  safe_pointer<T> get_object(const string &sql_query);

  //! returns only the first object of the sql query
  /*!
  this is an overloaded version of get_object which simplifies handling of parameters.
  */
  template <class U, class V, class W, class X, class Y>
  safe_pointer<T> get_object(const string &sql_query, const arg<U, V, W, X, Y> &a);

  //! returns the result of the sql query as a collection of objects
  /*!
  these objects are valid as long as object_record_set is valid.
  as soon as this object goes out of scope (or clear() is called, or get_objects is recalled)
  these objects will be released.

  sql_query must begin with "FROM" for example:
  "FROM person ORDER BY id;"
  or it can begin with a column list (the id must be included) for example:
  "id, firstname FROM person ORDER BY id;"
  or it can be global_empty_string to load all columns without any where-condition

  the returned [vector] pointer is never null, but the vector itself can be empty.
  */
  const vector<T*> *get_objects(const string &sql_query);

  //! returns the result of the sql query as a collection of objects
  /*!
  this is an overloaded version of get_objects which simplifies handling of parameters.
  be careful about sql injection, you have to escape ' manually.
  the returned [vector] pointer is never null, but the vector itself can be empty.
  */
  template <class U, class V, class W, class X, class Y>
  const vector<T*> *get_objects(const string &sql_query, const arg<U, V, W, X, Y> &a);

  //! flexible index operator, which can be used after calling get_object or get_objects
  T *operator[](unsigned int index) const;

  //! returns the number of objects which are loaded
  unsigned int get_object_count() const;

  //! reloads all objects from the database, which have been loaded by calling get_objects or get_object
  virtual void load();

  //! saves all objects to the database, which have been loaded by calling get_objects or get_object
  virtual void persist(bool requires_lock = true);

  //! removes all objects from the database, which have been loaded by calling get_objects or get_object
  virtual void remove();

  //! default constructor
  object_record_set(unsigned int initial_size = 0U);

  //! destructor
  virtual ~object_record_set();
};

////

template <class T>
void object_record_set<T>::clear() {
  for_each(objects.begin(), objects.end(), deleter());
  objects.clear();
}

template <class T>
safe_pointer<T> object_record_set<T>::get_object(const string &sql_query) {
  const vector<T*> *v1 = get_objects(sql_query);
  if (v1->empty() == true)
    return 0;
  else
    return safe_pointer<T>((*v1)[0]);
}

template <class T>
template <class U, class V, class W, class X, class Y>
safe_pointer<T> object_record_set<T>::get_object(const string &sql_query, const arg<U, V, W, X, Y> &a) {
  string q = sql_query;
  str::format(q, a);
  return get_object(q);
}

template <class T>
const vector<T*> *object_record_set<T>::get_objects(const string &sql_query) {
  clear();

  record_set rs;

  bool all_fields = true;
  if (sql_query.empty() == true) {
    string query = "SELECT * FROM ";
    query += T::get_table_name();
    database_serializable::get_database()->execute_query(query, rs); // to load all columns
  } else if (str::begins_with(sql_query, "FROM", false) == true)
    database_serializable::get_database()->execute_query("SELECT * " + sql_query, rs); // to load all columns
  else {
    database_serializable::get_database()->execute_query("SELECT " + sql_query, rs); // to load only the given columns
    all_fields = false;
  } // else

  const unsigned int rows = rs.get_row_count();
  unsigned int id_column_index = rs.get_column_index("id"); // lowercase
  if (id_column_index == UINT_MAX)
    id_column_index = rs.get_column_index("ID"); // uppercase

  if (id_column_index == UINT_MAX)
    throw db_exception("ID/id column could not be found!");

  objects.resize(rows, 0);

  #pragma omp parallel for if (rows > MINIMUM_SIZE_LIMIT_OMP)
  for (unsigned int i = 0; i < rows; ++i) {
    T *obj = new T();
    smart_pointer<T> sp(obj); // makes sure that no memory leak occurs in case of exceptions in the following loop; sp takes the ownership of the pointer

    obj->set_id(converter::to_unsigned_long_long(rs(i, id_column_index))); // may throw an exception
    obj->rs = &rs;
    obj->row = i;
    obj->loaded = true;

    if (all_fields == false) {
      for (unsigned int column = 0; column < rs.get_column_count(); ++column)
        obj->register_column(rs.get_column_name(column));
    } // if

    obj->load(); // may throw an exception
    obj->rs = 0; // reset
    obj->row = 0; // reset
    obj->loaded = false; // reset

    objects[i] = obj; // no exception occurred so far, so we have a valid object to add to the list
    sp.release(); // we take back the ownership of the pointer, it will be deleted by object_record_set
  } // for i

  return &objects;
}

template <class T>
template <class U, class V, class W, class X, class Y>
const vector<T*> *object_record_set<T>::get_objects(const string &sql_query, const arg<U, V, W, X, Y> &a) {
  string q = sql_query;
  str::format(q, a);
  return get_objects(q);
}

template <class T>
T *object_record_set<T>::operator[](unsigned int index) const {
  INDEX_CHECK(index, objects.size());
  return objects[index];
}

template <class T>
unsigned int object_record_set<T>::get_object_count() const {
  return objects.size();
}

template <class T>
void object_record_set<T>::load() {
  for_each(objects.begin(), objects.end(), mem_fun(&T::load));
}

template <class T>
void object_record_set<T>::persist(bool requires_lock) {
  if (requires_lock == true) {
    const char *table_name = T::get_table_name();

    try {
      database_serializable::get_database()->lock_table(table_name);
      for (size_t i = 0; i < objects.size(); ++i)
        objects[i]->persist(false);

      database_serializable::get_database()->unlock_table(table_name);
    } catch (const general_exception &ge) {
      database_serializable::get_database()->unlock_table(table_name);
      throw ge;
    } // catch
  } else {
    for (size_t i = 0; i < objects.size(); ++i)
      objects[i]->persist(false);

  } // else
}

template <class T>
void object_record_set<T>::remove() {
  for_each(objects.begin(), objects.end(), mem_fun(&T::remove));
}

template <class T>
object_record_set<T>::object_record_set(unsigned int initial_size /*= 0U*/) {
  objects.reserve(initial_size);
  for (unsigned int i = 0; i < initial_size; ++i)
    objects.push_back(new T());

}

template <class T>
object_record_set<T>::~object_record_set() {
  clear();
}

#endif
